package au.edu.swin.bank.teller;

import au.edu.swin.bank.ejb.session.stateless.AccountRemote;
import au.edu.swin.bank.ejb.session.stateless.CustomerRemote;
import au.edu.swin.bank.ejb.session.stateless.TransactionRemote;
import au.edu.swin.bank.persistence.entity.BankTransactionDTO;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import javax.ejb.EJB;

/**
 *
 * @author scott
 */
public class TellerClient {
    @EJB
    private static AccountRemote accountBean;
    @EJB
    private static CustomerRemote customerBean;
    @EJB
    private static TransactionRemote transactionBean;

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        //Display banner/welcome text
        println("SwinBank Teller Application");
        println("===========================");
        println("");
        println("This application is for SwinBank Teller staff only.");
        println("");

        //Collect credentials from the user and login
        boolean validPasswordEntered = false;
        while (!validPasswordEntered)
        {
            try
            {
                print("Teller ID: ");
                int staffId = readInt("Teller ID: ");
                print("Password: ");
                int staffPassword = readInt("Password: ");
                if (customerBean.staffLogin(staffId, staffPassword))
                {
                    println("Welcome, Teller #" + staffId + ".");
                    validPasswordEntered = true;
                    transactionBean.setCustId(staffId);
                }
                else
                {
                    throw new Exception("Login failed.");
                }
            }
            catch (Exception e)
            {
                println("Invalid username/password entered. Please try again.");
            }
        }

        //Display the main menu and do the appropriate function, based on their input
        while (true) {
            switch(displayMenu())
            {
                case 1: //Display accounts and their balances
                    println("== View customer account information ==");
                    displayAccounts();
                    break;
                case 2: //Transfer money
                    println("== Transfer ==");
                    doTransfer();
                    break;
                case 3: //Deposit
                    println("== Deposit ==");
                    doDeposit();
                    break;
                case 4: //Withdraw
                    println("== Withdraw ==");
                    doWithdraw();
                    break;
                case 5: //Open customer account
                    println("== Open customer account ==");
                    openAccount();
                    break;
                case 6: //Close customer account
                    println("== Close customer account ==");
                    closeAccount();
                    break;
                case 7: //View transaction history
                    println("== View transaction history ==");
                    viewTransactions();
                    break;
                case 8: //Exit
                    println("Thank you for using the SwinBank Teller system.");
                    return;
            }
            read();
        }
    }

    /**
     * Displays a list of the accounts held by the customer
     */
    private static boolean displayAccounts()
    {
        print("Customer ID: ");
        int custId = readInt("Customer ID: ");
        if (!customerBean.customerExists(custId))
        {
            println("A customer with the specified ID does not exist.");
            return false;
        }

        println("");
        println("This customer has the following accounts:");
        
        //Retrieve the account IDs as a Collection from the customer bean
        Collection<BigInteger> accountIDs = customerBean.getAccountIds(custId);

        if (accountIDs.size() > 0) {
            //Display each account id and its corresponding balance
            for (BigInteger accountID : accountIDs) {
                Double balance = accountBean.getBalance(accountID);
                println(accountID.toString() + " - $" + balance.toString());
            }
        }
        else {
            println("No accounts found for the specified customer.");
            return false;
        }
        return true;
    }

    /**
     * Transfers money from one account to another. Tellers can transfer between
     * any accounts, without restriction.
     */
    private static void doTransfer() {
        print("Source Account ID: ");
        BigInteger srcAccountId = BigInteger.valueOf(Long.parseLong(readInt("Source Account ID: ").toString()));
        print("Destination Account ID: ");
        BigInteger destAccountId = BigInteger.valueOf(Long.parseLong(readInt("Destination Account ID: ").toString()));
        print("Amount ($): ");
        double amount = readDouble("Amount ($): ");
        print("Description: ");
        String description = read();
        boolean transactionSuccessful = transactionBean.transferFunds(amount, srcAccountId, destAccountId, description);
        println(transactionSuccessful ? "Transfer was successful" : "Error - transfer was not successful.");
    }

    /**
     * Deposits funds into an account
     */
    private static void doDeposit() {
        if (displayAccounts()) {
            print("Account ID: ");
            BigInteger accountId = BigInteger.valueOf(Long.parseLong(readInt("Account ID: ").toString()));
            print("Amount ($): ");
            double amount = readDouble("Amount ($): ");
            print("Description: ");
            String description = read();
            boolean transactionSuccessful = transactionBean.depositFunds(amount, accountId, description);
            println(transactionSuccessful ? "Deposit was successful." : "Error - Deposit was not successful.");
        }
        else {
            println("Unable to perform deposit as no accounts are available for the specified customer.");
        }
    }

    /**
     * Withdraws funds from an account
     */
    private static void doWithdraw() {
        if (displayAccounts()) {
            print("Account ID: ");
            BigInteger accountId = BigInteger.valueOf(Long.parseLong(readInt("Account ID: ").toString()));
            print("Amount ($): ");
            double amount = readDouble("Account ($): ");
            print("Description: ");
            String description = read();
            boolean transactionSuccessful = transactionBean.withdrawFunds(amount, accountId, description);
            println(transactionSuccessful ? "Withdraw was successful." : "Error - Withdraw was not successful.");
        }
        else {
            println("Unable to perform withdrawal as no accounts are available for the specified customer.");
        }
    }

    /**
     * Opens an account for a customer with zero balance.
     */
    private static void openAccount() {
        print("Customer ID: ");
        Integer custId = readInt("Customer ID: ");
        boolean success = customerBean.openAccount(custId);
        println(success ? "Account was opened successfully" : "Error - Account was not opened successfully.");
    }

    /**
     * Closes an account held by a customer.
     */
    private static void closeAccount() {
        print("Account ID: ");
        BigInteger accountId = BigInteger.valueOf(Long.parseLong(readInt("Account ID: ").toString()));
        boolean success = customerBean.closeAccount(accountId);
        println(success ? "Account was closed successfully" : "Error - Account was not closed successfully.");
    }

    /**
     * Displays a list of transactions for an account.
     */
    private static void viewTransactions() {
        print("Account ID: ");
        
        //Get the account ID
        BigInteger accountId = BigInteger.valueOf(Long.parseLong(readInt("Account ID: ").toString()));

        //Get the start date
        print("Enter the start date (dd-mm-yyyy): ");

        DateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
        Date now = new Date();

        BigInteger startDate;
        
        try {
            startDate = BigInteger.valueOf(dateFormat.parse(read() + " 00:00:00").getTime());
        } catch (ParseException ex) {
            print("Invalid start date.");
            println("");
            return;
        }

        //Get the end date
        print("Enter the end date (dd-mm-yyyy): ");

        BigInteger endDate;

        try {
            endDate = BigInteger.valueOf(dateFormat.parse(read() + " " + now.getHours() + ":" + now.getMinutes() + ":" + now.getSeconds()).getTime());
        } catch (ParseException ex) {
            print("Invalid end date.");
            println("");
            return;
        }

        //Check the date range is valid
        if (!isDateRangeValid(startDate, endDate)) {
            println("Invalid date range specified.");
            println("Start date must be before end date and both dates must be in the past.");
            println("");
            return;
        }

        //Load the BankTransactionDTOs
        Collection<BankTransactionDTO> transactions = transactionBean.getTransactions(accountId, startDate, endDate);
        println("");

        if (transactions.size() > 0) {
            println("Date                           " + padString("From Account") + "   " + padString("To Account") + "   " + padString("Amount") + "   " + padString("Type") + "   " + padString("Description"));
            println("====                           " + padString("============") + "   " + padString("==========") + "   " + padString("======") + "   " + padString("====") + "   " + padString("===========")); //======================"); //96
            //Display each account id and its corresponding balance
            for (BankTransactionDTO transaction : transactions) {
                println(transaction.getDate().toString() + "   " +
                        padString(transaction.getSrcAccountId().toString()) + "   " +
                        padString(transaction.getDestAccountId().toString()) + "   " +
                        padString(String.valueOf(transaction.getAmount())) + "   " +
                        padString(transaction.getType()) + "   " +
                        transaction.getDescription());
            }
        }
        else {
            println("No transactions found for the specified account.");
        }
    }

    /**
     * Checks if the supplied date range is valid.
     * The start date must be before the end date and neither date can be in the future.
     * @param aStartDate The start date
     * @param aEndDate The end date
     * @return True if valid, otherwise false
     */
    private static boolean isDateRangeValid(BigInteger aStartDate, BigInteger aEndDate)
    {
        Date date = new Date();
        if (aStartDate.compareTo(aEndDate) < 0) {
            if (aEndDate.compareTo(BigInteger.valueOf(date.getTime())) < 0) {
                return true;
            }
        }
        return false;
    }

    /**
     * Adds spaces onto the end of the supplied string to pad it out to a specified length
     * @param input The string to pad
     * @return The padded string
     */
    private static String padString(String input)
    {
        String padded = input;
        for (int i = 0; i < (12 - input.length()); i++) //Don't go less than 12 here.
        {
            padded += " ";
        }
        return padded;
    }

    /**
     * Displays the main menu. This will call itself if an error occurs.
     * @return The number corresponding to the option selected by the user
     */
    private static int displayMenu() {
        //Display menu items
        println("Please select from the following options:");
        println("1: View customer account information");
        println("2: Transfer");
        println("3: Deposit");
        println("4: Withdraw");
        println("5: Open customer account");
        println("6: Close customer account");
        println("7: View transaction history");
        println("8: Exit");
        print("Selection: ");

        //Read in selection and handle any errors
        Integer input = readInt("Selection: ");
        if (input > 8 || input < 1)
        {
            println("Invalid selection, please try again.");
            println("");
            return displayMenu();
        }
        return input;
    }

    /**
     * Calls System.out.println(), it just makes it easier to call within the application
     * @param toPrint The string to print
     */
    private static void println(String toPrint) {
        System.out.println(toPrint);
    }

    /**
     * Calls System.out.print(), it just makes it easier to call within the application
     * @param toPrint The string to print
     */
    private static void print(String toPrint) {
        System.out.print(toPrint);
    }

    /**
     * Reads in input from the keyboard.
     * @return The input from the user.
     */
    private static String read() {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String input = "";
        try {
            input = reader.readLine();
        }
        catch (IOException e) {
            System.out.println("An input error occurred");
        }
        return input;
    }

    /**
     * Reads input from the user and converts it to an integer, handling any errors.
     * If invalid input is entered, the method will call itself and keep doing so until
     * valid input is given.
     * @param prompt The prompt to display at the cursor.
     * @return The Integer equivalent of the user input.
     */
    private static Integer readInt(String prompt) {
        try {
            return Integer.parseInt(read());
        }
        catch (NumberFormatException e) {
            println("Invalid input entered. Please enter only numbers.");
            print(prompt);
            return readInt(prompt);
        }
    }

    /**
     * Reads input from the user and converts it to a double, handling any errors.
     * If invalid input is entered, the method will call itself and keep doing so until
     * valid input is given.
     * @param prompt The prompt to display at the cursor.
     * @return The Double equivalent of the user input.
     */
    private static Double readDouble(String prompt) {
        try {
            return Double.parseDouble(read());
        }
        catch (NumberFormatException e) {
            println("Invalid input entered. Please enter only numbers.");
            print(prompt);
            return readDouble(prompt);
        }
    }
}
